/******************************************************************************
* Copyright (C) 2015  671643387@qq.com
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*****************************************************************************/

#ifndef NODE_DB_RDB_PREPARED_STATEMENT_H_
#define NODE_DB_RDB_PREPARED_STATEMENT_H_

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif

#include <cassert>
#include <cstdint>
#include <exception>
#include <string>
#include <vector>
#include <sstream>
#include "database.h"
#include "connection.h"

namespace node {
	namespace rdb {

		union StmtField
		{
			bool boolean;
			::std::uint8_t ui8;
			::std::int8_t i8;
			::std::uint16_t ui16;
			::std::int16_t i16;
			::std::uint32_t ui32;
			::std::int32_t i32;
			::std::uint64_t ui64;
			::std::int64_t i64;
			float f;
			double d;
		};

		enum StmtFieldType
		{
			FIELD_BOOL,
			FIELD_UI8,
			FIELD_UI16,
			FIELD_UI32,
			FIELD_UI64,
			FIELD_I8,
			FIELD_I16,
			FIELD_I32,
			FIELD_I64,
			FIELD_FLOAT,
			FIELD_DOUBLE,
			FIELD_STRING,
			FIELD_NONE
		};

		class StmtFieldData
		{
		public:
			StmtFieldData() : type_(FIELD_NONE) { binary_data_.ui64 = 0; }
			~StmtFieldData() {}

			template<typename T>
			StmtFieldData(T param) { Set(param); }

			template<typename T1>
			void Set(T1 param1);


			bool AsBool() const { assert(type_ == FIELD_BOOL); return (binary_data_.ui8 != 0); }
			::std::uint8_t AsUint8() const { assert(type_ == FIELD_UI8); return binary_data_.ui8; }
			::std::int8_t AsInt8() const { assert(type_ == FIELD_I8); return binary_data_.i8; }
			::std::uint16_t AsUint16() const { assert(type_ == FIELD_UI16); return binary_data_.ui16; }
			::std::int16_t AsInt16() const { assert(type_ == FIELD_I16); return binary_data_.i16; }
			::std::uint32_t AsUint32() const { assert(type_ == FIELD_UI32); return binary_data_.ui32; }
			::std::int32_t AsInt32() const { assert(type_ == FIELD_I32); return binary_data_.i32; }
			::std::uint64_t AsUint64() const { assert(type_ == FIELD_UI64); return binary_data_.ui64; }
			::std::int64_t AsInt64() const { assert(type_ == FIELD_I64); return binary_data_.i64; }
			float AsFloat() const { assert(type_ == FIELD_FLOAT); return binary_data_.f; }
			double AsDouble() const { assert(type_ == FIELD_DOUBLE); return binary_data_.d; }
			const char* AsStr() const { assert(type_ == FIELD_STRING); return string_data_.c_str(); }
			StmtFieldType get_type() const { return type_; }
			void* buff() const { return type_ == FIELD_STRING ? (void*)string_data_.c_str() : (void*)&binary_data_; }
			size_t get_size() const
			{
				switch (type_)
				{
				case FIELD_NONE:    return 0;
				case FIELD_BOOL:    // return sizeof(bool);
				case FIELD_UI8:     return sizeof(::std::uint8_t);
				case FIELD_UI16:    return sizeof(::std::uint16_t);
				case FIELD_UI32:    return sizeof(::std::uint32_t);
				case FIELD_UI64:    return sizeof(::std::uint64_t);
				case FIELD_I8:      return sizeof(::std::int8_t);
				case FIELD_I16:     return sizeof(::std::int16_t);
				case FIELD_I32:     return sizeof(::std::int32_t);
				case FIELD_I64:     return sizeof(::std::int64_t);
				case FIELD_FLOAT:   return sizeof(float);
				case FIELD_DOUBLE:  return sizeof(double);
				case FIELD_STRING:  return string_data_.length();

				default:
					throw ::std::runtime_error("unrecognized type of SqlStmtFieldType obtained");
				}
			}

		private:
			StmtFieldType type_;
			StmtField binary_data_;
			::std::string string_data_;
		};
		template<> inline void StmtFieldData::Set(bool val) { type_ = FIELD_BOOL; binary_data_.ui8 = val; }
		template<> inline void StmtFieldData::Set(::std::uint8_t val) { type_ = FIELD_UI8; binary_data_.ui8 = val; }
		template<> inline void StmtFieldData::Set(::std::int8_t val) { type_ = FIELD_I8; binary_data_.i8 = val; }
		template<> inline void StmtFieldData::Set(::std::uint16_t val) { type_ = FIELD_UI16; binary_data_.ui16 = val; }
		template<> inline void StmtFieldData::Set(::std::int16_t val) { type_ = FIELD_I16; binary_data_.i16 = val; }
		template<> inline void StmtFieldData::Set(::std::uint32_t val) { type_ = FIELD_UI32; binary_data_.ui32 = val; }
		template<> inline void StmtFieldData::Set(::std::int32_t val) { type_ = FIELD_I32; binary_data_.i32 = val; }
		template<> inline void StmtFieldData::Set(::std::uint64_t val) { type_ = FIELD_UI64; binary_data_.ui64 = val; }
		template<> inline void StmtFieldData::Set(::std::int64_t val) { type_ = FIELD_I64; binary_data_.i64 = val; }
		template<> inline void StmtFieldData::Set(float val) { type_ = FIELD_FLOAT; binary_data_.f = val; }
		template<> inline void StmtFieldData::Set(double val) { type_ = FIELD_DOUBLE; binary_data_.d = val; }
		template<> inline void StmtFieldData::Set(const char* val) { type_ = FIELD_STRING; string_data_ = val; }


		class Statement;

		class StmtParameters
		{
		public:
			typedef ::std::vector<StmtFieldData> ParameterContainer;

			explicit StmtParameters(::std::uint32_t nParams);
			~StmtParameters() {}

			::std::uint32_t GetBoundParamCount() const { return params_.size(); }
			void AddParam(const StmtFieldData& data) { params_.push_back(data); }
			void Reset(const Statement& stmt);
			void Swap(StmtParameters& obj);
			const ParameterContainer& get_params() const { return params_; }

		private:
			StmtParameters& operator=(const StmtParameters& obj);
			ParameterContainer params_;
		};

		class StatementID
		{
		public:
			StatementID() : initialized_(false) {}

			int ID() const { return index_; }
			::std::uint32_t get_arguments_count() const { return arguments_count_; }
			bool IsInitialized() const { return initialized_; }

		private:
			friend class Database;

			void init(int nID, ::std::uint32_t nArgs) { index_ = nID; arguments_count_ = nArgs; initialized_ = true; }

			int index_;
			::std::uint32_t arguments_count_;
			bool initialized_;
		};

		class Statement
		{
		public:
			~Statement() { delete params_; }


			Statement(const Statement& index) : index_(index.index_), db_(index.db_), params_(NULL)
			{
				if (index.params_)
				{
					params_ = new StmtParameters(*(index.params_));
				}
			}

			Statement& operator=(const Statement& index);

			int ID() const { return index_.ID(); }
			::std::uint32_t get_arguments_count() const { return index_.get_arguments_count(); }
			bool Execute();
			bool DirectExecute();


			template<typename ParamType1>
			bool PExecute(ParamType1 param1)
			{
				arg(param1);
				return Execute();
			}

			template<typename ParamType1, typename ParamType2>
			bool PExecute(ParamType1 param1, ParamType2 param2)
			{
				arg(param1);
				arg(param2);
				return Execute();
			}

			template<typename ParamType1, typename ParamType2, typename ParamType3>
			bool PExecute(ParamType1 param1, ParamType2 param2, ParamType3 param3)
			{
				arg(param1);
				arg(param2);
				arg(param3);
				return Execute();
			}

			template<typename ParamType1, typename ParamType2, typename ParamType3, typename ParamType4>
			bool PExecute(ParamType1 param1, ParamType2 param2, ParamType3 param3, ParamType4 param4)
			{
				arg(param1);
				arg(param2);
				arg(param3);
				arg(param4);
				return Execute();
			}

			// bind parameters with specified type
			void addBool(bool var) { Arg(var); }
			void addUInt8(::std::uint8_t var) { Arg(var); }
			void addInt8(::std::int8_t var) { Arg(var); }
			void addUInt16(::std::uint16_t var) { Arg(var); }
			void addInt16(::std::int16_t var) { Arg(var); }
			void addUInt32(::std::uint32_t var) { Arg(var); }
			void addInt32(::std::int32_t var) { Arg(var); }
			void addUInt64(::std::uint64_t var) { Arg(var); }
			void addInt64(::std::int64_t var) { Arg(var); }
			void addFloat(float var) { Arg(var); }
			void addDouble(double var) { Arg(var); }
			void addString(const char* var) { Arg(var); }
			void addString(const std::string& var) { Arg(var.c_str()); }
			void addString(std::ostringstream& ss) { Arg(ss.str().c_str()); ss.str(std::string()); }

		protected:
			// don't allow anyone except Database class to create static SqlStatement objects
			friend class Database;

			Statement(const StatementID& index, Database& db) : index_(index), db_(&db), params_(NULL) {}

		private:
			StmtParameters* Get()
			{
				if (!params_)
				{
					params_ = new StmtParameters(get_arguments_count());
				}

				return params_;
			}

			StmtParameters* Detach()
			{
				StmtParameters* p = params_ ? params_ : new StmtParameters(0);
				params_ = NULL;
				return p;
			}

			// helper function
			// use appropriate add* functions to bind specific data type
			template<typename ParamType>
			void Arg(ParamType val)
			{
				StmtParameters* p = Get();
				p->AddParam(StmtFieldData(val));
			}

			StatementID index_;
			Database* db_;
			StmtParameters* params_;
		};

		class PreparedStatement
		{
		public:
			virtual ~PreparedStatement() {}

			bool IsPrepared() const { return prepared_; }
			bool IsQuery() const { return query_; }
			::std::uint32_t get_params() const { return params_; }
			::std::uint32_t get_columns_count() const { return IsQuery() ? columns_count_ : 0; }

			virtual bool prepare() = 0;
			virtual void bind(const StmtParameters& holder) = 0;
			virtual bool execute() = 0;

		protected:
			PreparedStatement(const std::string& fmt, Connection& conn) :
				params_(0), columns_count_(0), query_(false),
				prepared_(false), format_(fmt), conn_(conn)
			{}

			::std::uint32_t params_;
			::std::uint32_t columns_count_;
			bool query_;
			bool prepared_;
			std::string format_;
			Connection& conn_;
		};


		class PlainPreparedStatement : public PreparedStatement
		{
		public:
			PlainPreparedStatement(const std::string& fmt, Connection& conn);
			~PlainPreparedStatement() {}


			virtual bool prepare() override { return true; }
			virtual void bind(const StmtParameters& holder) override;
			virtual bool execute() override;

		protected:
			void DataToString(const StmtFieldData& data, std::ostringstream& fmt);

			std::string plain_request_;
		};
	}
}

#endif // NODE_DB_RDB_PREPARED_STATEMENT_H_
